/* Copyright 2018 Intel Corporation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * @file
 * Uses AES-GCM 256, which also includes authentication.
 *
 * These are higher-level utility functions that use lower-level
 * functions in skenc.cpp, hashing, and random number generation.
 *
 * No OpenSSL/Mbed TLS-dependent code is present.
 * See skenc.cpp for OpenSSL/Mbed TLS-dependent code.
 */

#include "crypto_shared.h"
#include "crypto_utils.h" // RandomBitString(), ComputeMessageHash()
#include "error.h"
#include "utils.h"        // StrToByteArray()
#include "skenc.h"

namespace pcrypto = tcf::crypto;

// Error handling
namespace Error = tcf::error;

namespace constants = tcf::crypto::constants;


/**
 * Generate symmetric authenticated encryption key.
 * Throws RuntimeError.
 */
ByteArray pcrypto::skenc::GenerateKey() {
    return pcrypto::RandomBitString(constants::SYM_KEY_LEN);
}  // pcrypto::skenc::GenerateKey


/**
 * Generate a 96-bit symmetric authenticated encryption Initialization Vector
 * (IV). An IV is sometimes called a nonce.
 * Throws RuntimeError.
 *
 * @param IVstring string as input to hash in order to create an IV.
 *                 If "" or parameter is not present,
 *                 generate a random IV as input to hash
 * @returns IV as a byte array of 96-bits of binary data
 */
ByteArray pcrypto::skenc::GenerateIV(const std::string& IVstring) {
    // generate random IV if no input
    if (IVstring.compare("") == 0)
        return pcrypto::RandomBitString(constants::IV_LEN);
    // else use IVstring
    ByteArray hash = ComputeMessageHash(StrToByteArray(IVstring));
    hash.resize(constants::IV_LEN);
    return hash;
}  // pcrypto::skenc::GenerateIV


/**
 * Encrypt a message using AES-GCM authenticated encryption.
 * An IV is generated automatically and prepended to the encrypted data.
 * Throws RuntimeError, ValueError.
 *
 * @param message binary data to encrypt
 * @param key Secret AES-256 encryption key.
 *            Generated by GenerateKey()
 * @returns Byte array containing IV + encrypted data
 */
ByteArray pcrypto::skenc::EncryptMessage(const ByteArray& key,
        const ByteArray& message) {
    if (message.size() == 0) {
        std::string msg(
            "Crypto Error (EncryptMessage): Cannot encrypt the empty message");
        throw Error::ValueError(msg);
    }

    if (key.size() != constants::SYM_KEY_LEN) {
        std::string msg(
            "Crypto Error (EncryptMessage): Wrong AES-GCM key length");
        throw Error::ValueError(msg);
    }

    ByteArray iv = pcrypto::skenc::GenerateIV();
    ByteArray ct = pcrypto::skenc::EncryptMessage(key, iv, message);
    ct.insert(ct.begin(), iv.begin(), iv.end());
    return ct;
}  // pcrypto::skenc::EncryptMessage


/**
 * Decrypt message.data() using AES-GCM authenticated decryption.
 *
 * Expects a 12 byte (96 bit) IV (sometimes called a nonce) and
 * an 16 byte (128 bit) authentication tag (sometimes called a MAC),
 * prepended and appended, respectively, the input cipher text:
 *     message = ciphertext + authentication tag
 * The authentication tag is not encrypted.
 *
 * Throws RuntimeError, ValueError,
 * CryptoError (message authentication failure).
 *
 * @param key     Secret AES-256 encryption key.
 *                Generated by GenerateKey()
 * @param iv      96-bit initialization Vector (IV). Generated by GenerateIV()
 * @param message binary data to decrypt. Generated by EncryptMessage()
 *                Includes appended authentication tag.
 *                IV is separate (not prepended to message)
 * @returns Byte array containing decrypted data
 */
ByteArray pcrypto::skenc::DecryptMessage(
        const ByteArray& key, const ByteArray& iv, const ByteArray& message) {
    const char*  ct = (const char*)message.data();
    size_t ct_len = message.size();

    // Sanity check
    if (iv.size() != constants::IV_LEN) {
        std::string msg(
            "Crypto Error (DecryptMessage): Wrong AES-GCM IV length");
        throw Error::ValueError(msg);
    }

    return pcrypto::skenc::DecryptMessage(key,
            (const char*)iv.data(), ct, ct_len);
}  // pcrypto::skenc::DecryptMessage


/**
 * Decrypt message.data() using AES-GCM authenticated encryption.
 *
 * Expects a 12 byte (96 bit) IV (sometimes called a nonce) and
 * an 16 byte (128 bit) authentication tag (sometimes called a MAC),
 * prepended and appended, respectively, the input cipher text:
 *     message = IV + ciphertext + authentication tag
 * The IV and nonce are not encrypted and were were added by EncryptMessage().
 *
 * Throws RuntimeError, ValueError,
 * CryptoError (message authentication failure).
 *
 * @param key     Secret AES-256 encryption key.
 *                Generated by GenerateKey()
 * @param message binary data to decrypt. Generated by EncryptMessage()
 *                Includes 96 bit IV prepended to the binary data
 *                and 128 bit authentication tag
 * @returns Byte array containing decrypted data
 */
ByteArray pcrypto::skenc::DecryptMessage(const ByteArray& key,
        const ByteArray& message) {
    const char *iv = (char *)message.data();
    const char *ct = iv + constants::IV_LEN;
    int        ct_len = message.size() - constants::IV_LEN;

    // Sanity check
    if (message.size() < constants::IV_LEN + constants::TAG_LEN) {
        std::string msg(
            "Crypto Error (DecryptMessage): AES-GCM message smaller "
            "than minimum length (IV length + TAG length)");
        throw Error::ValueError(msg);
    }

    return DecryptMessage(key, iv, ct, ct_len);
}  // pcrypto::skenc::DecryptMessage
